Skip to content

feat(chat): highlight-to-chat — reference file/table selections in Chat - #6087

Open
mzxchandra wants to merge 28 commits into
stagingfrom
feat/highlight-to-chat
Open

feat(chat): highlight-to-chat — reference file/table selections in Chat#6087
mzxchandra wants to merge 28 commits into
stagingfrom
feat/highlight-to-chat

Conversation

@mzxchandra

Copy link
Copy Markdown
Contributor

Summary

IDE-style highlight → "Add to chat" for Files and Tables: select a passage in a file (Monaco or the Tiptap markdown editor) or rows/cells in a table, and it becomes a scoped reference chip the Sim agent can read, instead of attaching the whole resource. Cmd+C / Cmd+V works too: copy a selection and paste it into the chat as the same reference.

Adding a selection from a standalone Files/Tables page opens the Chat in the split layout (chat left, resource right) with the chip already in the input; adding it while a resource is already open in Chat drops the chip into the current conversation.

Type of Change

  • New feature

How it works

  • New ChatContext kinds file_selection / table_selection. File selections carry the literal text + line range inline; table selections carry authoritative row/column ids and are re-fetched server-side by id (never trusting client cell values), scoped to the table + workspace.
  • Producers use the Sim Chat block's Blimp icon in the Monaco context menu, the Tiptap bubble menu, and the table context menu.
  • Copy rides a custom text/x-sim-selection clipboard type alongside text/plain (bubble-phase listener so it survives the editors' own clearData()), read back by the chat paste handler.
  • Server resolves file selections to an inline citable snippet and table selections to a markdown table.

Testing

  • bunx tsc --noEmit: 0 errors. Biome clean. check:api-validation passes (new context fields live only on the domain validator + the ChatContext union — no boundary violation).
  • Unit tests: server resolution branches (process-contents), label/bounds helpers (selection-context), and the clipboard codec round-trip/validation (selection-clipboard, new).
  • Manual E2E in a headless browser: file selection → split view with the file open + chip; table selection → table tab + chip; copy → paste rebuilds the chip.
  • Pre-landing review (2 specialists): fixed a P2 where a >20k-char selection could overflow the server schema and reject the whole chat POST; hardened clipboard validation; extracted a shared copy hook; added clipboard tests.

Reviewers: focus on lib/copilot/chat/process-contents.ts (server row re-fetch + bounds) and the merge with staging's nuqs ?resource= refactor in home.tsx.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 1, 2026 7:56pm

Request Review

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches copilot request validation, server-side context resolution, and clipboard/handoff routing; bounded by caps and workspace checks, but large selections and select-all table paths add complexity around row loading and prompt size.

Overview
Adds highlight-to-chat: file and table selections become scoped file_selection / table_selection reference chips in Sim Agent Chat instead of attaching whole resources.

Producers: Add to Chat (Blimp) in Monaco and Tiptap file editors and in the table grid context menu; useSelectionCopyBridge attaches selection metadata on copy (bubble phase, skips nested INPUT so find-box copies don’t leak highlights). Cmd+C / Cmd+V uses custom text/x-sim-selection alongside text/plain; paste and programmatic insert go through prepareContextForInsert, uniqueContextLabel, and insertContextChips, with useContextManagement masking longer @label tokens so prefix labels don’t stick around.

Routing: useAddToChat tries live addMothershipContexts; otherwise MothershipHandoffStorage supports chip-only handoffs (accumulating contexts, expiry-aware) and navigation to home. onContextRemove now receives remaining so shared file/table slideover tabs stay open until no chip still references that resource.

Server: process-contents resolves file selections to fenced inline snippets (with line range and VFS path) and table selections to markdown tables by re-fetching rows by id with column scoping, character budgets, and schema caps from selection-context. Persisted messages keep fileName / tableName for chip UI but drop selection payloads (text, rowIds, etc.).

Reviewed by Cursor Bugbot for commit 7644b8d. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

IDE-style highlight-to-chat for Files and Tables: selection-scoped file_selection / table_selection chips, clipboard MIME bridge, server-side table row re-fetch, and split-layout handoff into Chat.

  • Adds ChatContext kinds and server resolution (inline file snippet; table markdown re-fetched by id).
  • Wires Monaco/Tiptap/table menus and copy/paste (text/x-sim-selection) into chat chips.
  • Opens Chat split view via handoff storage when adding from standalone Files/Tables pages.

Confidence Score: 3/5

Not fully safe to merge until multi-row table copy still attaches the selection MIME when the loaded-row chip path cannot run (empty loaded intersection or more than 500 selected rows).

Multi-row table copy can still fall through to an async text/plain-only clipboard write when writeLoadedRowsWithChip declines, so paste into Chat never rebuilds a table_selection chip on those paths. The earlier always-overwrite MIME bug for ordinary in-cap multi-row copies is fixed.

Files Needing Attention: apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx Adds table selection chips and a sync chip+copy path; multi-row paste still fails when the chip path declines and the async clipboard write is used.
apps/sim/lib/copilot/chat/process-contents.ts Resolves file/table selections server-side with workspace checks, row re-fetch, and content bounds.
apps/sim/lib/copilot/chat/selection-clipboard.ts Encodes/decodes selection contexts on the custom clipboard MIME with basic shape validation.
apps/sim/hooks/use-add-to-chat.ts Live chip insert or workspace-scoped handoff navigation when Chat is not mounted.

Sequence Diagram

sequenceDiagram
  participant User
  participant TableGrid
  participant Clipboard
  participant ChatInput
  participant Server as processContextsServer

  User->>TableGrid: Select rows/cells + Copy or Add to chat
  alt Sync loaded copy within row cap
    TableGrid->>Clipboard: text/plain + text/x-sim-selection
  else Paged/async copy path
    TableGrid->>Clipboard: text/plain only (ClipboardItem)
  end
  User->>ChatInput: Paste or live addMothershipContexts
  ChatInput->>ChatInput: Chip table_selection (ids only)
  ChatInput->>Server: Chat POST with contexts
  Server->>Server: getRowsByIds + markdown table
  Server-->>ChatInput: Resolved agent context
Loading

Reviews (17): Last reviewed commit: "fix(chat): compare selection ids as sets..." | Re-trigger Greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/copilot/chat/process-contents.ts Outdated
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/copilot/chat/selection-context.ts
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Follow-up on the previous round, now done rather than deferred.

I said the copy-path helpers being module-private in a ~4,600-line component with no test file was a real gap — the last two fixes to that logic were reasoned, not covered, and both were wrong on the first attempt (the chip-vs-text cap conflation, and before it the loaded-rows narrowing). That is precisely the code that should not be untested.

selectedColumnIds and buildTableSelectionContext now live in the existing table-grid/utils.ts, which already owns RowSelection, DisplayColumn and getColumnId — no new module for a single consumer. The copy decision is extracted as canWriteRowsWithChip, so writeLoadedRowsWithChip keeps only the clipboard and toast effects and utils.ts stays side-effect free.

utils.test.ts pins the distinction that was wrong twice:

✓ stays allowed past the chip row cap — the context caps itself
✓ defers past the text copy limit, which owns truncation

Restoring the old MAX_TABLE_SELECTION_ROWS gate fails both. Also covers the all-columns-collapse-to-open-scope rule, the row cap labelling the capped count rather than the requested one, and the column cap.

No behavior change in this commit — it is the same logic, relocated and covered.

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

…under-counting rows

Three findings from one Bugbot round.

Hidden columns widened cell ranges (reported twice). buildTableSelectionContext
and contextMenuColumnIds collapsed a range to an open scope when it covered
'every column', comparing against displayColumns.length — which drops hidden
columns AND expands workflow groups, so it never meant 'the whole schema'.
Selecting every visible column therefore cleared columnIds and the server
re-fetched columns the user had hidden. The collapse is removed rather than
re-based on a schema count: no count available to a caller describes the schema,
and an explicit column list is what the user actually selected. totalColumnCount
is gone from the signature.

Add-to-chat label undercounted rows. The menu derived its count from
selectedRowCount (loaded rows only) while the chip was built from the full
rowSel.ids set, so the label could promise fewer rows than were sent. Both now
read one addToChatRowIds memo, with the count passed through explicitly since it
legitimately differs from the count the delete/run labels use.

Monaco line range was off by one. A full-line highlight ends at column 1 of the
FOLLOWING line, so endLineNumber named a line that contributed no text — the
chip label and the agent prompt both claimed an extra line.

The collapse test I added last round asserted the buggy behavior as correct; it
now pins the opposite, and fails if the collapse returns.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Cursor Bugbot: last round's fix for the label undercounting rows introduced the
opposite error. addToChatRowCount passed the raw selection size, but
buildTableSelectionContext caps rowIds at MAX_TABLE_SELECTION_ROWS, so a
2,000-row selection advertised 2,000 while the chip referenced 500 — breaking
the same invariant the fix claimed to establish.

Routes the count through a chipRowCount helper next to the builder that applies
the cap, so the label cannot drift from the payload again.

utils.test.ts now asserts the invariant directly rather than the formula:
across 1, 42, 500, 750 and 50,000 requested rows, chipRowCount equals the
rowIds length the context actually carries. Verified to fail if the cap is
dropped.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Cursor Bugbot: the mention sync tests each label with a lookahead that rejects
only word characters, so '-', ')' and space all let a shorter label match INSIDE
a longer token. '@notes.md:12' matches within '@notes.md:12-40', and
'@sales (3 rows)' within '@sales (3 rows) (2)'. Deleting the shorter chip left
its context attached, and it was still sent with the message.

The label class is pre-existing, but this PR made it routine: line ranges and
uniqueContextLabel ordinals generate prefix pairs for any two selections of the
same file or table.

Fixed at the sync rather than by reshaping labels to dodge the prefix — a label
format chosen to avoid a matcher bug would just relocate it. Contexts are now
tested longest-label-first, and each matched token is blanked before shorter
labels are tested, so every context is judged against text its own token owns.
Blanked in place, not removed, so the (^|\s) boundary of whatever sits next to
it is preserved; prev order is still what's returned.

Shared with the workflow copilot input, so tests cover both directions: the two
prefix pairs are dropped when only the longer token remains, both survive when
both tokens are present, order is preserved, and trailing punctuation after a
mention still keeps its chip.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Cursor Bugbot: areContextsEqual compared only fileId and text for
file_selection, while the comment directly above claimed equality was the
selected range. A line that occurs twice in a file — a repeated import, a
closing brace — highlighted at both places produced identical text, so
prepareContextForInsert called the second a duplicate and dropped its chip,
even though the labels (notes.md:12 vs notes.md:50) were plainly different.

Comparing startLine/endLine as well makes the code match what the comment
promised. The rich-markdown editor omits the range, so both sides are undefined
there and identical text still dedupes — correct, since two identical passages
are genuinely indistinguishable without line numbers.

Tests cover both: distinct ranges stay distinct, an exact repeat still dedupes,
and the no-line-number path still dedupes. Verified the first fails against
text-only equality.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

…ll chip

Cursor Bugbot: for a gutter select-all the menu count comes from
selectedRowCount (capped), but the chip was built by loading exactly
MAX_TABLE_SELECTION_ROWS and filtering exclusions AFTER. Any excluded row inside
that prefix left the chip short of the advertised count.

Third appearance of the same invariant — label vs payload — this time in the
select-all path specifically, which the earlier fixes did not touch.

Loads the cap plus the exclusion count, which covers the worst case where every
exclusion falls inside the prefix, so the filtered result still reaches the cap
whenever the table has the rows. Extracted as drainTargetForChip so the
compensation is stated and tested rather than an inline arithmetic detail.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7644b8d. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants